package com.robertux.leaderboard.servlets;

import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.fileupload.FileItemIterator;
import org.apache.commons.fileupload.FileItemStream;
import org.apache.commons.fileupload.servlet.ServletFileUpload;
import org.apache.commons.io.IOUtils;
import org.apache.log4j.Logger;

import com.google.gson.JsonArray;
import com.google.gson.JsonObject;
import com.google.gson.JsonPrimitive;
import com.robertux.leaderboard.beans.JsonResponse;
import com.robertux.leaderboard.beans.RolUsuario;
import com.robertux.leaderboard.beans.UploadedFile;
import com.robertux.leaderboard.util.EvaluacionesFileContentHandler;
import com.robertux.leaderboard.util.ExcelHelper;
import com.robertux.leaderboard.util.FileContentHandler;
import com.robertux.leaderboard.util.FileUploadValidation;
import com.robertux.leaderboard.util.MethodExecutionSecurity;
import com.robertux.leaderboard.util.MethodParamsChecker;
import com.robertux.leaderboard.util.ProductsFileContentHandler;
import com.robertux.leaderboard.util.ProgresosFileContentHandler;
import com.robertux.leaderboard.util.UsersFileContentHandler;
import com.robertux.leaderboard.util.gestoresModelos.GestorGruposProductos;

/**
 * Servlet implementation class FileUploadServlet
 */
@WebServlet("/FileUploadServlet/*")
public class FileUploadServlet extends BaseServlet {
	private static final String FILE_CONTENT_HANDLER_ATTR = "fileContentHandler";
	public static final String LAST_UPLOAD_RESPONSE_ATTR = "lastUploadResponse";
	public static final String LAST_UPLOADED_CONTENT_ATTR = "lastUploadedContent";
	private static final long serialVersionUID = 1L;
	private Logger logger;
       
    /**
     * @see HttpServlet#HttpServlet()
     */
    public FileUploadServlet() {
        super();
        this.logger = Logger.getLogger(this.getClass());
    }

    @MethodExecutionSecurity(requireAuthentication=true, roleLevelAllowed=RolUsuario.ADMIN)
    @FileUploadValidation(maxFileSize=10000000L, validFileTypes={"xls", "xlsx"}, pageToReturn="/Leaderboard/admin/uploadContent.html")
	public JsonResponse uploadUsuarios(HttpServletRequest req, HttpServletResponse resp, UploadedFile file) throws IOException, ServletException {
		this.logger.debug("Archivo cargado y validado: " + file);
		String[][] contenidoArchivo = ExcelHelper.getFileContent(file.getContents(), file.getExt());
		List<String[]> contenidoArchivoFiltrado = Arrays.asList(contenidoArchivo);
		
		//Filtrando filas vacias
		Iterator<String[]> iterator = contenidoArchivoFiltrado.iterator();
		while(iterator.hasNext()) {
			String[] fila = iterator.next();
			if(fila.length == 0) {
				iterator.remove();
			}
		}
		
		req.getSession(true).setAttribute(LAST_UPLOADED_CONTENT_ATTR, contenidoArchivoFiltrado.toArray(new String[0][]));
		req.getSession(true).setAttribute(FILE_CONTENT_HANDLER_ATTR, new UsersFileContentHandler());
		
		return new JsonResponse();
	}
    
    @MethodExecutionSecurity(requireAuthentication=true, roleLevelAllowed=RolUsuario.ADMIN)
    @FileUploadValidation(maxFileSize=10000000L, validFileTypes={"xls", "xlsx"}, pageToReturn="/Leaderboard/admin/uploadContent.html")
    public JsonResponse uploadEvaluaciones(HttpServletRequest req, HttpServletResponse resp, UploadedFile file) throws IOException, ServletException {
    	this.logger.debug("Archivo cargado y validado: " + file);
		String[][] contenidoArchivo = ExcelHelper.getFileContent(file.getContents(), file.getExt());
		List<String[]> contenidoArchivoFiltrado = Arrays.asList(contenidoArchivo);
		
		//Filtrando filas vacias
		Iterator<String[]> iterator = contenidoArchivoFiltrado.iterator();
		while(iterator.hasNext()) {
			String[] fila = iterator.next();
			if(fila.length == 0) {
				iterator.remove();
			}
		}
		
		req.getSession(true).setAttribute(LAST_UPLOADED_CONTENT_ATTR, contenidoArchivoFiltrado.toArray(new String[0][]));
		req.getSession(true).setAttribute(FILE_CONTENT_HANDLER_ATTR, new EvaluacionesFileContentHandler());
		
		return new JsonResponse();
    }
    
	@MethodExecutionSecurity(requireAuthentication=true, roleLevelAllowed=RolUsuario.ADMIN)
    @FileUploadValidation(maxFileSize=10000000L, validFileTypes={"xls", "xlsx"}, pageToReturn="/Leaderboard/admin/uploadContent.html")
    public JsonResponse uploadProductos(HttpServletRequest req, HttpServletResponse resp, UploadedFile file) throws IOException, ServletException {
    	this.logger.debug("Archivo cargado y validado: " + file);
		String[][] contenidoArchivo = ExcelHelper.getFileContent(file.getContents(), file.getExt());
		List<String[]> contenidoArchivoFiltrado = Arrays.asList(contenidoArchivo);
		GestorGruposProductos gGprd = new GestorGruposProductos();
		
		//Filtrando filas vacias
		Iterator<String[]> iterator = contenidoArchivoFiltrado.iterator();
		while(iterator.hasNext()) {
			String[] fila = iterator.next();
			if(fila.length == 0) {
				iterator.remove();
			}
		}
		
		req.getSession(true).setAttribute(LAST_UPLOADED_CONTENT_ATTR, contenidoArchivoFiltrado.toArray(new String[0][]));
		this.logger.debug("GrupoProducto seleccionado: " + req.getSession().getAttribute("currentGPrdId"));
		req.getSession(true).setAttribute(FILE_CONTENT_HANDLER_ATTR, new ProductsFileContentHandler(gGprd.getGrupoProducto(Integer.parseInt(req.getSession().getAttribute("currentGPrdId").toString()))));
		
		return new JsonResponse();
    }
	
	@MethodExecutionSecurity(requireAuthentication=true, roleLevelAllowed=RolUsuario.ADMIN)
    @FileUploadValidation(maxFileSize=10000000L, validFileTypes={"xls", "xlsx"}, pageToReturn="/Leaderboard/admin/uploadContent.html")
    public JsonResponse uploadProgresos(HttpServletRequest req, HttpServletResponse resp, UploadedFile file) throws IOException, ServletException {
    	this.logger.debug("Archivo cargado y validado: " + file);
		String[][] contenidoArchivo = ExcelHelper.getFileContent(file.getContents(), file.getExt());
		List<String[]> contenidoArchivoFiltrado = Arrays.asList(contenidoArchivo);
		
		//Filtrando filas vacias
		Iterator<String[]> iterator = contenidoArchivoFiltrado.iterator();
		while(iterator.hasNext()) {
			String[] fila = iterator.next();
			if(fila.length == 0) {
				iterator.remove();
			}
		}
		
		req.getSession(true).setAttribute(LAST_UPLOADED_CONTENT_ATTR, contenidoArchivoFiltrado.toArray(new String[0][]));
		req.getSession(true).setAttribute(FILE_CONTENT_HANDLER_ATTR, new ProgresosFileContentHandler());
		
		return new JsonResponse();
    }
    
    @MethodExecutionSecurity(requireAuthentication=true, roleLevelAllowed=RolUsuario.ADMIN)
    @MethodParamsChecker(requiredParams={"mappedColumns", "discardedRows"})
    public JsonResponse insertUploadedContent(HttpServletRequest req, HttpServletResponse resp) throws IOException, ServletException {
    	if(req.getSession(true).getAttribute(LAST_UPLOADED_CONTENT_ATTR) != null && req.getSession(true).getAttribute(FILE_CONTENT_HANDLER_ATTR) != null) {
    		String[][] uploadedContent = (String[][])req.getSession(true).getAttribute(LAST_UPLOADED_CONTENT_ATTR);
    		FileContentHandler fileHandler = (FileContentHandler)req.getSession(true).getAttribute(FILE_CONTENT_HANDLER_ATTR);
    		
    		JsonResponse jsonResp = new JsonResponse();
    		JsonArray arr = new JsonArray();
    		String[] insertResults = fileHandler.handleFileContent(uploadedContent, 
    				req.getParameter("discardedRows").split(","), req.getParameter("mappedColumns").split(","));
    		
    		for(String result: insertResults) {
    			arr.add(new JsonPrimitive(result));
    		}
    		
    		jsonResp.add("results", arr);
    		return jsonResp;
    	} else {
    		return JsonResponse.fromError(1043, LAST_UPLOADED_CONTENT_ATTR, FILE_CONTENT_HANDLER_ATTR);
    	}
    }

	@MethodExecutionSecurity(requireAuthentication=true, roleLevelAllowed=RolUsuario.ADMIN)
    public JsonResponse getLastUploadedContent(HttpServletRequest req, HttpServletResponse resp) throws IOException, ServletException {
    	if(req.getSession(true).getAttribute(LAST_UPLOADED_CONTENT_ATTR) != null && req.getSession(true).getAttribute(FILE_CONTENT_HANDLER_ATTR) != null) {
    		String[][] uploadedContent = (String[][])req.getSession(true).getAttribute(LAST_UPLOADED_CONTENT_ATTR);
    		FileContentHandler fileHandler = (FileContentHandler)req.getSession(true).getAttribute(FILE_CONTENT_HANDLER_ATTR);
    		
    		JsonResponse jsonResp = new JsonResponse();
    		JsonArray rowsArr = new JsonArray();
    		JsonArray dataColumnsArr = new JsonArray();
    		
    		for(String columnName: fileHandler.getMappingColumns()) {
    			dataColumnsArr.add(new JsonPrimitive(columnName));
    		}
    		int maxColumns = 0;
    		
    		for(String[] fila: uploadedContent) {
    			maxColumns = Math.max(maxColumns, fila.length);
    			JsonObject obj = new JsonObject();
    			JsonArray columnsArr = new JsonArray();
    			for(String columna: fila) {
    				columnsArr.add(new JsonPrimitive(columna));
    			}
    			obj.addProperty("discarded", false);
    			obj.addProperty("insertResult", "");
    			obj.add("columns", columnsArr);
    			rowsArr.add(obj);
    		}
    		jsonResp.add("uploadedContent", rowsArr);
    		jsonResp.add("maxColumns", new JsonPrimitive(maxColumns));
    		jsonResp.add("dataColumns", dataColumnsArr);
    		jsonResp.add("pageToReturn", new JsonPrimitive(fileHandler.getPageToReturn()));
    		return jsonResp;
    	} else {
    		return JsonResponse.fromError(1043, LAST_UPLOADED_CONTENT_ATTR, FILE_CONTENT_HANDLER_ATTR);
    	}
    }
    
    protected UploadedFile getUploadedFile(HttpServletRequest req) {
    	UploadedFile upldFile = null;
    	ServletFileUpload fileUpload = new ServletFileUpload();
		try {
			FileItemIterator iterator = fileUpload.getItemIterator(req);
			while(iterator.hasNext()) {
				FileItemStream itemStream = iterator.next();
				InputStream stream = itemStream.openStream();
				if(!itemStream.isFormField()) {
					this.logger.info("Archivo " + itemStream.getName() + " cargado al servidor.");
					String fileName = itemStream.getName();
					upldFile = new UploadedFile(fileName, fileName.substring(fileName.lastIndexOf(".")+1));
					upldFile.setContents(IOUtils.toByteArray(stream));
				}
			}
		} catch (Exception e) {
			this.logger.error("Error tratando de interpretar el request para cargar el archivo: " + e.getMessage(), e);
		}
		return upldFile;
    }
    
    protected JsonResponse isMethodValid(Method method, HttpServletRequest req, String httpMethod, UploadedFile file) {
    	JsonResponse isBaseMethodValid = super.isMethodValid(method, req, httpMethod); 
    	if(isBaseMethodValid.getCode() > 0) {
    		return isBaseMethodValid;
    	}
    	
    	if(!ServletFileUpload.isMultipartContent(req)) {
    		return JsonResponse.fromError(1042);
    	}
    	
    	if(!method.isAnnotationPresent(FileUploadValidation.class)) {
    		return JsonResponse.fromError(1042);
    	} else {
    		FileUploadValidation fuValidation = method.getAnnotation(FileUploadValidation.class);
    		if(file == null) {
    			return JsonResponse.fromError(1042);
    		} else {
    			if(file.getContents().length > fuValidation.maxFileSize()) {
    				return JsonResponse.fromError(1040);
    			}
    			boolean isValidType = false;
    			for(String validType: fuValidation.validFileTypes()) {
    				if(file.getExt().equalsIgnoreCase(validType)) {
    					isValidType = true;
    					break;
    				}
    			}
    			if(!isValidType) {
    				return JsonResponse.fromError(1041);
    			}
    		}
    	}
    	
    	return new JsonResponse();
    }
    
    @Override
    public void handleHttp(HttpServletRequest req, HttpServletResponse resp, String httpMethod) throws ServletException, IOException {
    	this.logger.debug("Request: " + httpMethod + " " + req.getRequestURL().toString());
    	String action = getRequestAction(req);
    	String pageToReturn = "/Leaderboard/error.html";
    	Method method = null;
    	
    	try {
    		try {
				method = this.getClass().getMethod(action, HttpServletRequest.class, HttpServletResponse.class);
				super.handleHttp(req, resp, httpMethod);
				return;
			} catch (NoSuchMethodException e) {
				this.logger.warn("Metodo " + action + " no encontrado con parámetros base. Se buscará con parámetros de upload: " + e.getMessage());
			}
			method = this.getClass().getMethod(action, HttpServletRequest.class, HttpServletResponse.class, UploadedFile.class);
			if(method.isAnnotationPresent(FileUploadValidation.class)) {
				pageToReturn = method.getAnnotation(FileUploadValidation.class).pageToReturn();
			}
			UploadedFile file = this.getUploadedFile(req);
			JsonResponse validCode = this.isMethodValid(method, req, httpMethod, file);
			if(validCode.getCode() == 0) {
				String missingParam = this.getMissingParameters(method, req);
				if(missingParam.trim().length() == 0) {
					JsonResponse jsonResp = (JsonResponse)method.invoke(this, req, resp, file);
					req.getSession(true).setAttribute(LAST_UPLOAD_RESPONSE_ATTR, jsonResp);
					this.logger.info("Regresando a página " + pageToReturn);
					redirectToPage(resp, pageToReturn, jsonResp.getCode());
					return;
				} else {
					req.getSession(true).setAttribute(LAST_UPLOAD_RESPONSE_ATTR, JsonResponse.fromError(1005, missingParam, action));
					redirectToPage(resp, pageToReturn, 1005);
					return;
				}
			} else {
				req.getSession(true).setAttribute(LAST_UPLOAD_RESPONSE_ATTR, validCode);
				this.logger.info("uploadResponse: " + validCode);
				redirectToPage(resp, pageToReturn, validCode.getCode());
				return;
			}
		} catch (Exception e) {
			this.logger.error("Excepción: " + e.getMessage(), e);
			if(e instanceof NoSuchMethodException) {
				req.getSession(true).setAttribute(LAST_UPLOAD_RESPONSE_ATTR, JsonResponse.fromError(1002, action));
				redirectToPage(resp, pageToReturn, 1002);
				return;
			} else {
				req.getSession(true).setAttribute(LAST_UPLOAD_RESPONSE_ATTR, JsonResponse.fromError(1003, action));
				redirectToPage(resp, pageToReturn, 1003);
				return;
			}
		}
    }

	protected void redirectToPage(HttpServletResponse resp, String pageToReturn) throws IOException {
		resp.sendRedirect(pageToReturn);
	}
	
	protected void redirectToPage(HttpServletResponse resp, String pageToReturn, int errCode) throws IOException {
		if(errCode > 0) {
			String separator = pageToReturn.contains("?")? "&": "?";
			pageToReturn += separator + "errCode=" + errCode;
		}
		this.redirectToPage(resp, pageToReturn);
	}

}
